local phy = {}

local gfx = require "libs/gfx"

ffi.cdef [[
	typedef struct cpArray cpArray;
	typedef struct cpSpaceHash cpSpaceHash;
	typedef struct cpContactBufferHeader cpContactBufferHeader;
	typedef struct cpHashSet cpHashSet;
	typedef struct cpArbiter cpArbiter;
	typedef struct cpShapeClass cpShapeClass;
	typedef void* cpDataPointer;
	typedef unsigned cpTimestamp;
	typedef unsigned cpCollisionType;
	typedef unsigned cpGroup;
	typedef unsigned cpLayers;
	typedef unsigned cpHashValue;
	typedef int cpBool;

	typedef double cpFloat;
	typedef struct cpVect { cpFloat x,y; } cpVect;
	typedef struct cpBB { cpFloat l, b, r, t; } cpBB;

	typedef void (*cpBodyVelocityFunc)(struct cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt);
	typedef void (*cpBodyPositionFunc)(struct cpBody *body, cpFloat dt);

	typedef cpBool (*cpCollisionBeginFunc)(cpArbiter *arb, struct cpSpace *space, void *data);
	typedef cpBool (*cpCollisionPreSolveFunc)(cpArbiter *arb, struct cpSpace *space, void *data);
	typedef void (*cpCollisionPostSolveFunc)(cpArbiter *arb, struct cpSpace *space, void *data);
	typedef void (*cpCollisionSeparateFunc)(cpArbiter *arb, struct cpSpace *space, void *data);

	typedef struct cpComponentNode {
		struct cpBody *parent;
		struct cpBody *next;
		int rank;
		cpFloat idleTime;
	} cpComponentNode;

	typedef struct cpBody {
		
		cpBodyVelocityFunc velocity_func;
		cpBodyPositionFunc position_func;
		cpFloat m, m_inv;
		cpFloat i, i_inv;
		cpVect p, v, f;
		cpFloat a, w, t;
		cpVect rot;
		cpDataPointer data;
		cpFloat v_limit, w_limit;

		cpVect v_bias_private;
		cpFloat w_bias_private;
		struct cpSpace *space_private;
		struct cpShape *shapesList_private;
		cpComponentNode node_private;
	} cpBody;

	typedef struct cpCollisionHandler {
		cpCollisionType a;
		cpCollisionType b;
		cpCollisionBeginFunc begin;
		cpCollisionPreSolveFunc preSolve;
		cpCollisionPostSolveFunc postSolve;
		cpCollisionSeparateFunc separate;
		void *data;
	} cpCollisionHandler;

	typedef struct cpSpace {
		int iterations;
		int elasticIterations;
		cpVect gravity;
		cpFloat damping;
		cpFloat idleSpeedThreshold;
		cpFloat sleepTimeThreshold;

		int locked_private;
		cpTimestamp stamp_private;
		cpSpaceHash *staticShapes_private;
		cpSpaceHash *activeShapes_private;
		cpArray *bodies_private;
		cpArray *sleepingComponents_private;
		cpArray *rousedBodies_private;
		cpArray *arbiters_private;
		cpArray *pooledArbiters_private;
		cpContactBufferHeader *contactBuffersHead_private;
		cpContactBufferHeader *_contactBuffersTail_Deprecated_private;
		cpArray *allocatedBuffers_private;
		cpHashSet *contactSet_private;
		cpArray *constraints_private;
		cpHashSet *collFuncSet_private;
		cpCollisionHandler defaultHandler_private;
		cpHashSet *postStepCallbacks_private;
	
		cpBody staticBody;
	} cpSpace;

	typedef struct cpShape {
		const cpShapeClass *klass_private;

		cpBody *body;
		cpBB bb;
		cpBool sensor;
		cpFloat e;
		cpFloat u;
		cpVect surface_v;
		cpFloat density;
		cpDataPointer data;
		cpCollisionType collision_type;
		cpGroup group;
		cpLayers layers;
	
		struct cpShape *next_private;
		cpHashValue hashid_private;
	} cpShape;

	typedef struct cpCircleShape {
		cpShape shape;
	
		cpVect c_private;
		cpFloat r_private;
		cpVect tc_private;
	} cpCircleShape;

	void cpInitChipmunk(void);

	cpFloat cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset);

	cpSpace* cpSpaceInit(cpSpace *space);
	void cpSpaceDestroy(cpSpace *space);
	cpBody *cpSpaceAddBody(cpSpace *space, cpBody *body);
	void cpSpaceStep(cpSpace *space, cpFloat dt);

	cpBody *cpBodyInit(cpBody *body, cpFloat m, cpFloat i);
	void cpBodyUpdateMassMoment(cpBody *body);
	cpShape *cpSpaceAddShape(cpSpace *space, cpShape *shape);

	cpCircleShape *cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpFloat radius, cpVect offset);
]]


local p = gfx.g
local C = ffi.C
local cpVect = ffi.typeof "cpVect"
phy.vect = cpVect

p.cpInitChipmunk()

-- TODO: p.cpSpaceDestroy(space)

function phy.create_space(gravity)
	local space = ffi.new("cpSpace")
	p.cpSpaceInit(space)
	space.gravity = gravity or cpVect(0, 100)
	return {space, bodies = {}, shapes = {}}
end

function phy.create_body(space, mass, radius)
	local body = ffi.new("cpBody")
	p.cpBodyInit(body, mass, 1)
	space.bodies[#space.bodies + 1] = body -- To keep it alive
	p.cpSpaceAddBody(space[1], body)
	return body
end

function phy.create_circle(space, body, radius, pos)
	local shape = ffi.new("cpCircleShape")
	p.cpCircleShapeInit(shape, body, radius, pos or cpVect(0, 0))
	space.shapes[#space.shapes + 1] = shape -- To keep it alive
	p.cpSpaceAddShape(space[1], shape.shape)
	return shape
end

function phy.balance_body(body)
	p.cpBodyUpdateMassMoment(body)
end

function phy.step(space)
	p.cpSpaceStep(space[1], 0.01)
end

return phy